iT邦幫忙

2023 iThome 鐵人賽

DAY 28
0

昨天幫 ALB 加上 CDN 但還沒開啟 cache,今天要來實驗一下 CDN 的 cache 機制~

cloudfront distribution 的 cache 相關參數是透過 cache policy 設定的,我們會先新增一個 cache policy、設置相關參數,接著在 distribution 多加一個 behavior,開啟某個 path pattern 的 cache 功能。(本日程式碼

新增 cloudfront cache policy

cloudfront cache policy 的 resource 是 aws_cloudfront_cache_policy

resource "aws_cloudfront_cache_policy" "cached" {
  name        = "my-app-caching"
  default_ttl = 600
  max_ttl     = 86400
  min_ttl     = 60
  parameters_in_cache_key_and_forwarded_to_origin {
    cookies_config {
      cookie_behavior = "none"
    }
    headers_config {
      header_behavior = "none"
    }
    query_strings_config {
      query_string_behavior = "none"
    }
    enable_accept_encoding_brotli = true
    enable_accept_encoding_gzip   = true
  }
}

default_ttlmax_ttlmin_ttl 是設定 cloudfront cache 的 expire time,單位是秒。打開壓縮相關的支援,enable_accept_encoding_brotlienable_accept_encoding_gzip 設為 true。其他 cache key 相關的設定先不設。

cloudfront distribution 增加 behavior

先在 Laravel 加一個 api /api/demo ,讓它回傳 Hello Laravel 的字串,用這個 api 當作要 cache 的 api 示範。

接著在 terraform aws_cloudfront_distribution.cdn 裡增加一個 ordered_cache_behavior block:

ordered_cache_behavior {
  allowed_methods          = ["HEAD", "GET"]
  cached_methods           = ["HEAD", "GET"]
  path_pattern             = "/api/demo"
  target_origin_id         = aws_lb.alb.dns_name
  viewer_protocol_policy   = "redirect-to-https"
  cache_policy_id          = aws_cloudfront_cache_policy.cached.id
  origin_request_policy_id = data.aws_cloudfront_origin_request_policy.all_viewer.id
}

path_pattern 設定 /api/demo ,表示當 request 是要到這個 path 時,會使用這個 behavior。path pattern 除了可以寫定一個 path 之外,也可以用 wildcard 如 * ,詳細設定方式可參考 文件cache_policy_id 使用剛剛新增的 cache policy 的 id。其他部份跟 default behavior 一樣。

查看 cache 的作用

terraform apply 後(cdn distribution 通常需要幾分鐘),就能用瀏覽器 access /api/demo 看 cloudfront cache 的作用~

這是第一次連,沒有在 cloudfront hit 到 cache:

https://ithelp.ithome.com.tw/upload/images/20231008/20160671PLtQIZdeAa.png

第二次可以看到有 hit 到 cloudfront cache 了:

https://ithelp.ithome.com.tw/upload/images/20231008/20160671I0hUFg13oQ.png

這就是最基本的 cloudfront distribution caching 設定,其他更複雜的 cache 參數設定就要依據 application 的需求設置。

CodePipeline 增加清理 cdn cache

cloudfront distribution 有 cache 就有「什麼時候要清掉舊資料 cache」的問題,顯而易見的,在 deploy Laravel 後要清 cdn cache,因為可能新 container 產生的結果已經跟 cdn cache 不同,這時候我們應該要主動清理 cdn cache 以免使用者拿到舊資料。

cloudfront distribution 清理 cache 的動作稱為 Invalidation,可以從網頁也可以透過 API 等方式進行,下圖是網頁介面:

https://ithelp.ithome.com.tw/upload/images/20231008/20160671D0fYcYj8Ze.png

想要清 cache 的時候就建立一個 invalidation,它會讓我們選擇要清 cache 的 path,設定 path 後建立 invalidation,cloudfront 就會開始清相關的 cache。因為 cloudfront 是 global 層級的服務,完成操作會需要幾分鐘。

既然是 deploy 後要清理 cdn cache,最適合觸發 cache 清理動作的地方當然是 codepipeline 啦~我們要在 codepipeline 的 deploy stage 後面加一個 stage 來清理指定的 cloudfront distribution。要用 codepipeline 做清 cdn cache 的動作當然不可能用網頁……我們會用 AWS 的 lambda function 來實作~

Lambda function 是 AWS 一個 serverless 的服務,讓我們可以不用管執行的 server 與環境等等 infrastructure,直接在 AWS 上執行 function。Lambda function 支援的執行環境有 .Net、Go、Java、Python 跟 Ruby,我們只要把 function 寫好丟上去就能直接執行了!當然有費用囉~

我們會用 python 配合 AWS SDK 來寫 invalidate cloudfront distribution 的程式,把這個 python 程式丟上 Lambda function,再由 CodePipeline trigger 執行,達到我們想在 deploy ECS 後清 cdn cache 的流程。

開始實作吧~

首先新增讓 lambda function 放 log 的 cloudwatch log group:

resource "aws_cloudwatch_log_group" "invalidate_cdn_cache_lambda" {
  name = "/aws/lambda/my-app-invalidate-cdn-cache"
}

接著新增要給 lambda function 用的 IAM role 及 policy,讓 lambda function 可以 invalidate cloudfront distribution cache、寫 log 跟回報 codepipeline 結果:

resource "aws_iam_role" "invalidate_cdn_cache_lambda" {
  name = "my-app-invalidate-cdn-cache-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Action = "sts:AssumeRole",
        Principal = {
          Service = "lambda.amazonaws.com"
        },
        Effect = "Allow",
      }
    ]
  })
}

resource "aws_iam_role_policy" "invalidate_cdn_cache_lambda" {
  name = "my-app-invalidate-cdn-cache-policy"
  role = aws_iam_role.invalidate_cdn_cache_lambda.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "logs:CreateLogGroup",
        ]
        Effect   = "Allow"
        Resource = "arn:aws:logs:${var.region}:${var.account_id}:*"
      },
      {
        Effect = "Allow"
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:${aws_cloudwatch_log_group.invalidate_cdn_cache_lambda.name}:*"
      },
      {
        Effect = "Allow"
        Action = [
          "codepipeline:PutJobFailureResult",
          "codepipeline:PutJobSuccessResult",
          "cloudfront:CreateInvalidation"
        ]
        Resource = "*"
      }
    ]
  })
}

再來是用 python 寫的 invalidate cloudfront distribution cache 的 function,把它存成 invalidate_cdn_cache.py

import json
import boto3

code_pipeline = boto3.client("codepipeline")
cloud_front = boto3.client("cloudfront")

def lambda_handler(event, context):
    job_id = event["CodePipeline.job"]["id"]
    try:
        user_params = json.loads(
            event["CodePipeline.job"]
                ["data"]
                ["actionConfiguration"]
                ["configuration"]
                ["UserParameters"]
        )
        cloud_front.create_invalidation(
            DistributionId=user_params["distributionId"],
            InvalidationBatch={
                "Paths": {
                    "Quantity": len(user_params["objectPaths"]),
                    "Items": user_params["objectPaths"],
                },
                "CallerReference": event["CodePipeline.job"]["id"],
            },
        )
    except Exception as e:
        code_pipeline.put_job_failure_result(
            jobId=job_id,
            failureDetails={
                "type": "JobFailed",
                "message": str(e),
            },
        )
    else:
        code_pipeline.put_job_success_result(
            jobId=job_id,
        )

最後是壓縮 python 程式的 data source 以及 lambda function resource:

data "archive_file" "invalidate_cdn_cache" {
  type        = "zip"
  source_file = "invalidate_cdn_cache.py"
  output_path = "invalidate_cdn_cache.zip"
}

resource "aws_lambda_function" "invalidate_fe_cdn_cache" {
  filename         = "invalidate_cdn_cache.zip"
  function_name    = "my-app-invalidate-cdn-cache"
  role             = aws_iam_role.invalidate_cdn_cache_lambda.arn
  handler          = "invalidate_cdn_cache.lambda_handler"
  source_code_hash = data.archive_file.invalidate_cdn_cache.output_base64sha256
  runtime          = "python3.10"
  timeout          = 300
}

這邊我們用到新 module archive 來幫 source code 做壓縮,所以 plan 或 apply 前要先安裝 archive module:

$ terraform init -upgrade

aws_lambda_function resource 的 handler 要設定程式的執行入口,在 python 的格式是 FILENAME.FUNCTION_NAME ,我們的 python 是 invalidate_cdn_cache.py 、裡面要給 lambda 使用的 function 是 lambda_handler ,所以 handler"invalidate_cdn_cache.lambda_handler"source_code_hash 參數是用來決定要不要更新 lambda function 的,它是由 invalidate_cdn_cache.zip 經過 base64 encode 後再算 SHA256 hash 得到的。如果 python 程式碼有改動,這個 hash 值就會改變,也就會觸發 lambda resource 的更新。timeout 是這個 lambda function 最久可以執行多長時間,單位是秒。因為 invalidate distribution cache 需要幾分鐘,所以建議設長一點,否則可能出現實際上 invalidation 有成功,但因為超過 timeout 被認為 lambda function 執行失敗導致 codepipeline 失敗的 false alarm。

最後是在 Codepipeline 的最後增加一個 AfterDeploy stage,裡面包含 invalidate cloudfront distribution 的 action:

stage {
  name = "AfterDeploy"
  action {
    category = "Invoke"
    configuration = {
      FunctionName = aws_lambda_function.invalidate_fe_cdn_cache.function_name
      UserParameters = jsonencode(
        {
          distributionId = aws_cloudfront_distribution.cdn.id
          objectPaths    = ["/*"]
        }
      )
    }
    input_artifacts  = []
    name             = "InvalidateCDNCache"
    output_artifacts = []
    owner            = "AWS"
    provider         = "Lambda"
    region           = var.region
    run_order        = 1
    version          = "1"
  }
}

apply resource 上去後,可以在 codepipeline 最後多了一個 stage,每次 deploy 後都會清理 cdn 的 cache:

https://ithelp.ithome.com.tw/upload/images/20231008/20160671f9MoJNPUtQ.png

執行 codepipeline 後,也可以在 cloudfront distribution 裡看到執行完成的 invalidation:

https://ithelp.ithome.com.tw/upload/images/20231008/20160671FrJsd4A0vc.png


上一篇
Day 27 加上 CDN
下一篇
Day 29 ECS Cluster Auto Scaling
系列文
AWS ECS + Gitlab + Laravel + Terraform 從入門到摔坑30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言